/**
 * 
 */
package br.fot.granola.oboe.core;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;

/**
 * @author Felipe
 *
 */
/**
 * Primary Oboe singleton class. Must be initialized before any Oboe functionality is used. 
 */
public class Oboe
{
	private static final Hashtable< Class<? extends Object>, Hashtable<String, Method> > globalSlotIndex = new Hashtable<Class<? extends Object>, Hashtable<String,Method>>();

	private class Node
	{
		public Node parent;
		public HashSet<Node> children;
		public Object ref;
		public Hashtable< String, Method > slotIndex;
		public Hashtable< String, HashSet<Node> > childSlots;
		
		
		public Node( Object ref )
		{
			this.ref = ref;
			this.children = new HashSet<Oboe.Node>();
			this.childSlots = new Hashtable<String, HashSet<Node>>();
			Class<? extends Object> def = ref.getClass();
			
			if( !globalSlotIndex.contains( def ) )
			{
				Hashtable<String, Method> slots = new Hashtable<String, Method>();
				recurse( slots, def );
				globalSlotIndex.put( def, slots );
				this.slotIndex = slots;
			}
			else
			{
				this.slotIndex = globalSlotIndex.get( def );
			}
			
			this.addToSlotIndex( this );
		}
		
		private void recurse( Hashtable<String, Method> slots, Class<? extends Object> def )
		{
			Class<? extends Object> sc = def.getSuperclass();
			if( sc != null ) recurse( slots, sc );
			
			Method[] methods = def.getMethods();
			for( Method m : methods )
			{
				if( m.getAnnotation( Slot.class ) instanceof Slot )
				{
					slots.put( m.getName(), m );
				}
			}
		}
		
		public void addChild( Node n )
		{
			if( n.parent != null )
			{
				n.parent.removeChild( n );
			}
			
			n.parent = this;
			this.children.add( n );
			this.addToSlotIndex( n );
		}
		
		public void removeChild( Node n )
		{
			if( n.parent != this ) return;
			n.parent = null;
			this.children.remove( n );
			this.removeFromSlotIndex( n );
		}
		
		public void addCollection( Node n )
		{
			this.children.add( n );
			this.addToSlotIndex( n );
		}
		
		public void removeCollection( Node n )
		{
			this.children.remove( n );
			this.removeFromSlotIndex( n );
		}

		private void addToSlotIndex( Node n )
		{
			Hashtable<String, Method> slots = globalSlotIndex.get( n.ref.getClass() );
			for( String s : slots.keySet() )
			{
				if( !this.childSlots.containsKey( s ) )
				{
					this.childSlots.put( s, new HashSet<Node>() );
				}
				this.childSlots.get( s ).add( n );
			}
		}
		
		private void removeFromSlotIndex( Node n )
		{
			Hashtable<String, Method> slots = globalSlotIndex.get( n.ref.getClass() );
			for( String s : slots.keySet() )
			{
				if( this.childSlots.containsKey( s ) )
				{
					HashSet<Node> set = this.childSlots.get( s );
					set.remove( n );
				}
			}
		}
	}
	
	/**
	 * a Dictionary that can be used to pass flags to the rest of the project.
	 */
	public static final HashMap<String, Object> flags = new HashMap<String, Object>(); 
	
	private static Oboe instance = new Oboe();
	
	/**
	 * global translator function (Translate.run)
	 * @see Translator
	 */
	// public static var translator:Function = null;
	
	private static final HashMap<Object, Node> nodeIndex = new HashMap<Object, Oboe.Node>();
	
	private static Node getNode( Object o, boolean create )
	{
		Node ret = null;
		if( nodeIndex.containsKey(o) )
		{
			ret = nodeIndex.get(o);
		}
		else if( create )
		{
			ret = instance.new Node(o);
			nodeIndex.put( o, ret );
		}
		return ret;
	}
	
	/**
	 * Adds a child sigslot to a parent sigslot so that it may communicate. 
	 * @param parent the parent sigslot
	 * @param child the child sigslot
	 */
	public static void addToCollection( Object parent, Object child )
	{
		if( child == null || child == parent || parent == null ) return; 
		Node pn = getNode( parent, true );
		Node cn = getNode( child, true );
		pn.addCollection( cn );
	}
	
	/**
	 * Removes a child from the specified parent 
	 * @param parent the sigslot the child belongs to
	 * @param child the child to remove
	 * 
	 */		
	public static void removeFromCollection( Object parent, Object child )
	{
		if( child == null || child == parent || parent == null ) return; 
		Node pn = getNode( parent, false );
		Node cn = getNode( child, false );
		if( pn != null && cn != null ) pn.removeCollection( cn );
	}	
	/**
	 * Adds a child sigslot to a parent sigslot so that it may communicate. 
	 * @param parent the parent sigslot
	 * @param child the child sigslot
	 */
	public static void addChild( Object parent, Object child )
	{
		if( child == null || child == parent || parent == null ) return; 
		Node pn = getNode( parent, true );
		Node cn = getNode( child, true );
		pn.addChild( cn );
		Object[] param = { pn.ref };
		_message( cn, "onAddToParent", param );
		param[0] = cn.ref;
		_message( pn, "onAddChild", param );
	}
	
	/**
	 * Removes a child from the specified parent 
	 * @param parent the sigslot the child belongs to
	 * @param child the child to remove
	 * 
	 */		
	public static void removeChild( Object parent, Object child )
	{
		if( child == null || child == parent || parent == null ) return; 
		Node pn = getNode( parent, false );
		Node cn = getNode( child, false );
		if( pn != null && cn != null )
		{
			Object[] param = { pn.ref };
			_message( cn, "onRemoveFromParent", param );
			param[0] = cn.ref;
			_message( pn, "onRemoveChild", param );
			pn.removeChild( cn );
		}
	}
	
	/**
	 * sends a signal directly to the target sigslot 
	 * @param target the sigslot that will receive the signal
	 * @param name the name of the slot function to call
	 * @param params the parameters of the slot function
	 * 
	 */		
	public static Object message( Object target, String name, Object... params )
	{
		if( target == null ) return null;
		
		Node n = getNode( target, true );
		Method m = n.slotIndex.get( name );
		if( m != null )
		{
			try {
				return m.invoke(target, params);
			} catch (IllegalArgumentException e) {
			} catch (IllegalAccessException e) {
			} catch (InvocationTargetException e) {
			}
		}
		return null;
	}
	
	/**
	 * sends a signal directly to the target sigslot 
	 * @param target the sigslot that will receive the signal
	 * @param name the name of the slot function to call
	 * @param params the parameters of the slot function
	 * 
	 */		
	public static Object messageArray( Object target, String name, Object[] params )
	{
		if( target == null ) return null;
		
		Node n = getNode( target, true );
		Method m = n.slotIndex.get( name );
		if( m != null )
		{
			try {
				return m.invoke(target, params);
			} catch (IllegalArgumentException e) {
			} catch (IllegalAccessException e) {
			} catch (InvocationTargetException e) {
			}
		}
		return null;
	}	
	
	public static void _message( Node n, String name, Object[] params )
	{
		Method m = n.slotIndex.get( name );
		if( m != null )
		{
			try {
				m.invoke( n.ref, params );
			} catch (IllegalArgumentException e) {
			} catch (IllegalAccessException e) {
			} catch (InvocationTargetException e) {
			}
		}
	}
	
	
	/**
	 * sends a signal to a sigslot's parent, siblings and children. 
	 * @param sender The sigslot that is sending the signal.
	 * @param name The name of the signal.
	 * @param params Parameters to be passed to the slots.
	 * @return a Vector.<Object/> with the return values of the slots. 
	 * 
	 */
	public static void signal( Object sender, String name, Object... params )
	{
		if( sender == null ) return;
		Node n = getNode( sender, false );
		if( n == null ) return;
		if( n.parent != null )
		{
			HashSet<Node> slots = n.parent.childSlots.get(name);
			if( slots != null )
			{
				for( Node c : slots )
				{
					try {
						c.slotIndex.get( name ).invoke( c.ref, params );
					} catch (IllegalArgumentException e) {
					} catch (IllegalAccessException e) {
					} catch (InvocationTargetException e) {
					}
				}
			}
		}
		
		HashSet<Node> slots = n.childSlots.get(name);
		if( slots != null )
		{
			for( Node c : n.childSlots.get(name) )
			{
				try {
					c.slotIndex.get( name ).invoke( c.ref, params );
				} catch (IllegalArgumentException e) {
				} catch (IllegalAccessException e) {
				} catch (InvocationTargetException e) {
				}
			}
		}
	}
	
	public static void signalUp( Object sender, String name, Object... params )
	{
		if( sender == null ) return;
		Node n = getNode( sender, false );
		if( n == null ) return;
		if( n.parent != null )
		{
			HashSet<Node> slots = n.parent.childSlots.get(name);
			if( slots != null )
			{
				for( Node c : slots )
				{
					try {
						c.slotIndex.get( name ).invoke( c.ref, params );
					} catch (IllegalArgumentException e) {
					} catch (IllegalAccessException e) {
					} catch (InvocationTargetException e) {
					}
				}
			}
		}
	}	
	/**
	 * Sends a signal to all of a sigslot's children. 
	 * @param sender the sigslot sending the signal
	 * @param name the name of the signal/slot
	 * @param params any parameters to pass to the slot.
	 * @return returns a Vector.<Object/> containing the slots' returns.
	 * 
	 */
	public static void signalDown( Object sender, String name, Object... params )
	{
		if( sender == null ) return;
		Node n = getNode( sender, false );
		if( n == null ) return;
		HashSet<Node> slots = n.childSlots.get(name);
		if( slots != null )
		{
			for( Node c : slots )
			{
				if( c == n ) continue;
				try {
					c.slotIndex.get( name ).invoke( c.ref, params );
				} catch (IllegalArgumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	
	public static void destroy( Object o )
	{
		if( o == null ) return;
		Node n = getNode( o, false );
		if( n == null ) return;
		_message( n, "onDestroy", null );
		if( n.parent != null ) removeChild( n.parent, n );
		n.ref = null;
		nodeIndex.remove( o );
	}
	
}
